package ca.ubc.eece310.project;

/**
 * EECE 310 Term Project Fall 2009
 * @author Matthew Fong, Herman Lee, Scott Pearson
 */

import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jnetpcap.Pcap;
import org.jnetpcap.PcapBpfProgram;
import org.jnetpcap.PcapIf;
import org.jnetpcap.packet.PcapPacket;
import org.jnetpcap.packet.PcapPacketHandler;

public class PacketSniffer extends Thread {

	/*******************************************************************************
	 * OVERVIEW: PacketSniffer is intended to be run as a separate Thread, that
	 * will capture packets from a specified network interface, and then store
	 * those packets somewhere that can be accessed and read.
	 * Rep-invariant: device != null
	 * AF(c): c.device, c.before, c.after, c.pcap
	 */
	private PcapIf device;
	private static StringBuilder errbuf = new StringBuilder(); // For any error
																// msgs
	private Date before, after;
	private Pcap pcap;
	final private int millisecondsToScan = 1000;

	// CONSTRUCTOR
	public PacketSniffer(PcapIf device) {
		/**
		 * REQUIRES: device != null 
		 * EFFECTS: Initializes a PacketSniffer which
		 * catches packets from device
		 */
		this.device = device;
	}

	public void run() {
		/**
		 * MODIFIES: lastPackets from the main class, BandwidthLog 
		 * EFFECTS: Captures packets for a time denoted by millisecondsToScan and stores
		 * them in MyPackets in lastPackets
		 */
		int snaplen = 64 * 1024; // Capture all packets, no trucation
		int flags = Pcap.MODE_PROMISCUOUS; // capture all packets
		int timeout = 10 * 1000; // 10 seconds in millis
		pcap = Pcap.openLive(device.getName(), snaplen, flags, timeout, errbuf);
		if (pcap == null) {
			System.err.printf("Error while opening device for capture: %s\n",
					errbuf.toString());
			return;
		}

		PcapPacketHandler<String> pph = new PcapPacketHandler<String>() {
			public void nextPacket(PcapPacket packet, String user) {

//				after = new Date();
//				if (after.getTime() - before.getTime() >= millisecondsToScan) {
//					pcap.breakloop();
//					BandwidthLog.timePassed = (int) (after.getTime() - before
//							.getTime());
//				}

				try {
					BandwidthLog.lastPackets.add(packetParser(packet));
				} catch (Exception e) {
				}
				after = new Date();
				if (after.getTime() - before.getTime() >= millisecondsToScan) {
					BandwidthLog.timePassed = (int)(after.getTime() - before.getTime());
					pcap.breakloop();
				}
			}
		};

		// filter(pcap, "port 80");
		before = new Date();
		pcap.loop(5000, pph, "HAHAHA");
		pcap.close();
	}

	@SuppressWarnings("unused")
	private void filter(Pcap pcap, String arg) {
		/**
		 * EFFECTS: Creates a filter on which packets to capture
		 */
		PcapBpfProgram filter = new PcapBpfProgram();
		String expression = arg;
		int optimize = 0; // 1 means true, 0 means false
		int netmask = 0xFFFFFF00; // 255.255.255.0

		int r = pcap.compile(filter, expression, optimize, netmask);
		if (r != Pcap.OK) {
			System.out.println("Filter error: " + pcap.getErr());
		}
		pcap.setFilter(filter);
	}

	private MyPacket packetParser(PcapPacket p) throws MalformedIPException {
		/**
		 * EFFECTS: Parses the information from PcapPacket's toString() method
		 * into a MyPacket and returns it
		 */
		String packetString = p.toString();
		String source = new String(), destination = new String();
		int size = p.size(), sourcePort = 0, destinationPort = 0;

		if (size <= 0)
			size = 0;

		Pattern pat = Pattern.compile("source = \\d*\\.\\d*\\.\\d*\\.\\d*\n");
		Matcher m = pat.matcher(packetString);
		String s = new String();
		while (m.find()) {
			source = m.group();
			Pattern ip = Pattern.compile("\\d*\\.\\d*\\.\\d*\\.\\d*");
			Matcher ipm = ip.matcher(source);
			ipm.find();
			source = ipm.group();
		}

		pat = Pattern.compile("destination = \\d*\\.\\d*\\.\\d*\\.\\d*\n");
		m = pat.matcher(packetString);
		while (m.find()) {
			destination = m.group();
			Pattern ip = Pattern.compile("\\d*\\.\\d*\\.\\d*\\.\\d*");
			Matcher ipm = ip.matcher(destination);
			ipm.find();
			destination = ipm.group();
		}

		pat = Pattern.compile("source = \\d*\n");
		m = pat.matcher(packetString);
		while (m.find()) {
			s = m.group();
			sourcePort = Integer.parseInt(s.substring(s.indexOf(" = ")
					+ " = ".length(), s.length() - 1));
		}

		pat = Pattern.compile("destination = \\d*\n");
		m = pat.matcher(packetString);
		while (m.find()) {
			s = m.group();
			destinationPort = Integer.parseInt(s.substring(s.indexOf(" = ")
					+ " = ".length(), s.length() - 1));
		}

		if (source.equals("") || destination.equals(""))
			throw new MalformedIPException();
		return new MyPacket(source, destination, size, sourcePort,
				destinationPort);
	}
	public boolean repOk() {
		/**
		 * EFFECTS: Returns true if the rep invariant holds for this
		 * 			else returns false
		 */
		if(device!=null)
			return true;
		return false;
	}
	
	public String toString() {
		/**
		 * EFFECTS: Returns a String representation
		 */
		return device.toString() + "\n Started sniffing at: " + before.toString() + "\n now at: " + after.toString();
	}
}
